/*
   Copyright (c) [2023] [魔凤啸天]
   [MFUI] is licensed under Mulan PSL v2.
   You can use this software according to the terms and conditions of the Mulan PSL v2. 
   You may obtain a copy of Mulan PSL v2 at:
            http://license.coscl.org.cn/MulanPSL2 
   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
   See the Mulan PSL v2 for more details.  
*/
#include "listlayout.hpp"

namespace MFUI{
    ListLayout::ListLayout(unsigned int ndisplay,SDL_Color back,
	std::initializer_list<Node*>& children,float scrollpercent):
	Button(children),displayitem(ndisplay),begin(0),background(back),
	dragged(false),scrollchanged(true),inscroll(false),scrollpercent(scrollpercent){}
	ListLayout::~ListLayout(){SDL_DestroyTexture(cache);}
	void ListLayout::onremove(Window* w){
		w->disconnect(SDL_MOUSEWHEEL,this);
		w->disconnect(SDL_MOUSEBUTTONDOWN,this);
		w->disconnect(SDL_MOUSEMOTION,this);
		w->disconnect(SDL_MOUSEBUTTONUP,this);
		auto& arr=children;
		size_t size=arr.size();
		if(size){
			size_t i=begin,t=displayitem,n=0;
			while(n++<t){
				if(i==size)return;
				arr[i++]->onremove(w);
			}
		}
	}
	void ListLayout::setdisplayitem(Window* w,unsigned int ndisplay){
		if(displayitem<ndisplay){
			auto children=this->children;
			auto size=children.size();
			auto oldbegin=begin;
			auto newbegin=std::min(size-ndisplay,oldbegin);
			while(newbegin<oldbegin)children[--oldbegin]->onadd(w);
			oldbegin+=displayitem;
			size=std::min(size,newbegin+ndisplay);
			while(oldbegin<size)children[--size]->onadd(w);
			begin=newbegin;
			displayitem=ndisplay;
			redraw=true;
			notifyparent();
			return;
		}
		if(ndisplay<displayitem){
			auto children=this->children;
			auto size=children.size();
			auto oldbegin=begin;
			begin=std::min(size-ndisplay,oldbegin);
			auto newbegin=begin;
			while(newbegin<oldbegin)children[newbegin++]->onadd(w);
			oldbegin=std::min(oldbegin+displayitem,size);
			newbegin+=ndisplay;
			while(newbegin<oldbegin)children[--oldbegin]->onremove(w);
			displayitem=ndisplay;
			redraw=true;
			notifyparent();
		}
	}
	unsigned int ListLayout::getdisplayitem()const{return displayitem;}
	bool ListLayout::confirmdraw(){return redraw|notified|scrollchanged;}
	void ListLayout::scrollcallback(Window* w,ListLayout* v,SDL_Event* e){
		auto& arr=v->children;
		auto size=arr.size();
		if(size){
			auto& r=v->getrect();
			auto x=e->wheel.mouseX,y=e->wheel.mouseY;
			auto rx=r.x,ry=r.y;
			if(rx<=x&&x<=rx+r.w&&ry<=y&&y<=ry+r.h){
				v->scroll(w,e->wheel.x,e->wheel.y);
			}
		}
	}
	void ListLayout::_losefocus(Window* w){
		dragged=false;
		if(inscroll){
			inscroll=false;
			scrollchanged=true;
			notifyparent();
		}
	}
	void ListLayout::mouseexited(Window* w){
		if(!dragged){
			inscroll=false;
			scrollchanged=true;
			notifyparent();
		}
	}
	bool ListLayout::_scroll(Window* w,size_t newindex){
		auto& arr=children;
		if(begin!=newindex){
			size_t i=begin;
			if(newindex<i){
				size_t t=SDL_min(i-newindex,displayitem);
				i+=displayitem;
				for(size_t k=0;k<t;k++)arr[--i]->onremove(w);
				begin=newindex;
				while(t--)arr[newindex++]->onadd(w);
				return true;
			}
			size_t t=i+displayitem;
			if(i<newindex&&newindex<t){
				auto j=t;
				t+=newindex-i;
				while(i<newindex)arr[i++]->onremove(w);
				while(j<t)arr[j++]->onadd(w);
				begin=newindex;
				return true;
			}
			while(i<t)arr[i++]->onremove(w);
			i=newindex,t=i+displayitem;
			while(i<t)arr[i++]->onadd(w);
			begin=newindex;
			return true;
		}
		return false;
	}
	void ListLayout::onadd(Window* w){
		w->connect(SDL_MOUSEWHEEL,this,(Window::EventCallback)scrollcallback);
		w->connect(SDL_MOUSEMOTION,this,(Window::EventCallback)mousemotioncallback);
		w->connect(SDL_MOUSEBUTTONDOWN,this,(Window::EventCallback)mousebuttondowncallback);
		w->connect(SDL_MOUSEBUTTONUP,this,(Window::EventCallback)mousebuttonupcallback);
		auto& arr=children;
		size_t size=arr.size();
		if(size){
			size_t i=begin,t=displayitem,n=0;
			while(n++<t){
				if(i==size)return;
				arr[i++]->onadd(w);
			}
		}
	}
	void ListLayout::removechild(Window* w,size_t index){
		auto sum=begin+displayitem;
		if(begin<=index&&index<sum){
			if(sum<children.size())children[sum]->onadd(w);
			Node::removechild(w,index);
			redraw=true;
			notifyparent();
			return;
		}
		auto iter=children.begin()+index;
		auto v=*iter;
		v->setparent(nullptr),v->deref();
		children.erase(iter);
		redraw=true;
		notifyparent();
	}
	void ListLayout::removechildren(Window* w,size_t start,size_t end){
		if(start<end){
			auto t=begin+displayitem;
			auto iter=children.begin();
			if(begin<=start&&start<t){
				auto i=iter+start,e=iter+SDL_min(t,end);
				while(i!=e){
					auto v=*(i++);
					v->onremove(w);
					v->setparent(nullptr);
					v->deref();
				}
				e=iter+end;
				while(i<e){
					auto v=*(i++);
					v->setparent(nullptr);
					v->deref();
				}
				t-=start;
				e=children.end();
				while(i<e){
					if(t--){
						(*(i++))->onadd(w);
						continue;
					}
					children.erase(iter+start,iter+std::min(end,children.size()));
					redraw=true;
					notifyparent();
					return;
				}
				children.erase(iter+start,iter+std::min(end,children.size()));
				redraw=true;
				notifyparent();
				return;
			}
			auto b=iter+start,e=iter+end;
			while(b!=e){
				auto v=*(b++);
				v->setparent(nullptr);
				v->deref();
			}
			children.erase(iter+start,e);
			redraw=true;
			notifyparent();
		}
	}
	void ListLayout::popchildren(Window* w,size_t n){
		auto size=children.size();
		ListLayout::removechildren(w,size-n,size);
	}
	void ListLayout::addchild(Window* w,Node* n){
		if(begin+displayitem>children.size()){
			Node::addchild(w,n);
			redraw=true;
			notifyparent();
			return;
		}
		children.emplace_back(n);
		n->setparent(this),n->ref();
		scrollchanged=true;
		notifyparent();
	}
	void ListLayout::addchildren(Window* w,std::initializer_list<Node*> l){
		auto size=children.size(),sum=begin+displayitem;
		children.insert(children.end(),l.begin(),l.end());
		for(auto v:l)v->ref(),v->setparent(this);
		if(sum>size){
			sum=SDL_min(sum-size,l.size());
			auto begin=l.begin();
			while(sum--)(*begin++)->onadd(w);
			redraw=true;
			notifyparent();
			return;
		}
		scrollchanged=true;
		notifyparent();
	}
	void ListLayout::insertchild(Window* w,Node* n,size_t index){
		auto sum=begin+displayitem;
		if(begin<=index&&index<sum){
			Node::insertchild(w,n,index);
			children[sum]->onremove(w);
			redraw=true;
			notifyparent();
			return;
		}
		children.insert(children.begin()+index,n);
		n->setparent(this),n->ref();
		scrollchanged=true;
		notifyparent();
	}
	void ListLayout::insertchildren(Window* w,std::initializer_list<Node*> l,size_t index){
		auto sum=begin+displayitem;
		if(begin<=index&&index<sum){
			auto add=SDL_min(sum-index,l.size());
			auto& arr=children;
			sum=SDL_min(sum,arr.size());
			while(index<sum)arr[index++]->onremove(w);
			arr.insert(children.begin()+index,l.begin(),l.end());
			for(auto n:l)n->setparent(this),n->ref();
			auto begin=l.begin();
			while(add--)(*begin++)->onadd(w);
			redraw=true;
			notifyparent();
			return;
		}
		children.insert(children.begin()+index,l.begin(),l.end());
		for(auto n:l)n->setparent(this),n->ref();
		scrollchanged=true;
		notifyparent();
	}
	void ListLayout::swapchild(size_t a,size_t b){
		if(a!=b){
			auto na=children[a],nb=children[b];
			children[b]=na,children[a]=nb;
			std::swap((SDL_FRect&)na->getrect(),(SDL_FRect&)nb->getrect());
			na->forceredraw();
			nb->forceredraw();
			if(begin<=a&&a<begin+displayitem||
			begin<=b&&b<begin+displayitem){
				notified=true;
				notifyparent();
			}
		}
	}
	void ListLayout::clear(Window* w){
		removechildren(w,0,children.size());
		begin=0;
	}
	VListLayout::VListLayout(unsigned int ndisplay,SDL_Color back,
	std::initializer_list<Node*> children,float scrollpercent):
	ListLayout(ndisplay,back,children,scrollpercent){}
	void VListLayout::draw(Renderer* r,bool updatecache){
		auto w=rect.w,h=rect.h,y=rect.y;
		auto sw=w*scrollpercent;
		auto& arr=children;
		auto size=arr.size();
		size_t i=begin;
		if(redraw|updatecache){
			CacheNode::_draw(r,updatecache);
			redraw=false;
			notified=false;
			size_t t=SDL_min(displayitem,size),n=0;
			if(displayitem)unit=(float)h/displayitem;else unit=0.F;
			itemrect.x=rect.x,itemrect.y=y;
			itemrect.w=w-sw,itemrect.h=h/displayitem;
			r->copyF(cache,nullptr,&rect);
			r->setdrawcolor(background);
			r->fillrectF(&rect);
			if(size){
				while(n++<t){
					if(i==size)break;
					auto cur=arr[i++];
					(SDL_FRect&)cur->getrect()=itemrect;
					cur->forceredraw();
					cur->draw(r,true);
					itemrect.y+=itemrect.h;
				}
			}
			goto _vlistlayout_scrollchanged;
		}
		if(scrollchanged){
			_vlistlayout_scrollchanged:
			total=h*((float)begin/size);
			SDL_Rect _src{(int)(w-sw),0,(int)sw,(int)h};
			if(displayitem<size)scrollrect={rect.x+_src.x,y+total,round(sw),((float)displayitem/size)*h};
			else scrollrect={rect.x+_src.x,y+total,round(sw),h};
			r->setdrawcolor({0,0,0,255});
			SDL_FRect _{scrollrect.x,y,scrollrect.w,h};
			r->fillrectF(&_);
			r->copyF(cache,&_src,&_);
			auto back=background.a;
			r->setdrawcolor(background);
			r->fillrectF(&_);
			if(dragged)back>>=2;
			else if(inscroll)back>>=1;
			r->setdrawcolor({(Uint8)~background.r,(Uint8)~background.g,(Uint8)~background.b,back});
			r->fillrectF(&scrollrect);
			scrollchanged=false;
		}
		if(notified){
			notified=false;
			if(size){
				size_t t=displayitem,n=0;
				i=begin;
				while(n++<t){
					if(i==size){
						makecircle(r,rect);
						return;
					}
					auto cur=arr[i++];
					if(cur->confirmdraw())cur->draw(r,updatecache);
				}
			}
		}
		makecircle(r,rect);
	}
	void VListLayout::scroll(Window* w,int x,int y){
		if(y>0&&y>begin)return scrollto(w,(size_t)0);
		scrollto(w,begin-y);
	}
	void VListLayout::pressed(Window* w,SDL_FPoint& p){
		auto& r=rect;
		auto rx=r.x,x=p.x,ry=r.y,y=p.y,rh=r.h;
		if(scrollrect.x<=x&&x<=rx+r.w&&ry<=y&&y<=ry+rh){
			dragged=true;
			inscroll=true;
			scrollchanged=true;
			notifyparent();
			mousemove(w,p);
		}
	}
	void VListLayout::released(Window* w,SDL_FPoint& p){
		if(dragged){
			dragged=false;
			auto& r=rect;
			auto rx=r.x,x=p.x,ry=r.y,y=p.y,rh=r.h;
			if(scrollrect.x>x||x>rx+r.w||ry>y||y>ry+rh)inscroll=false;
			scrollchanged=true;
			notifyparent();
		}
	}
	void VListLayout::mousemove(Window* w,SDL_FPoint p){
		if(dragged){
			scrollto(w,(p.y-rect.y)/rect.h);
			return;
		}
		auto& r=rect;
		auto rx=r.x,x=p.x,ry=r.y,y=p.y,rh=r.h;
		if(scrollrect.x<x&&x<=rx+r.w&&ry<=y&&y<=ry+rh){
			scrollchanged=true;
			inscroll=true;
			notifyparent();
			return;
		}
		if(inscroll){
			inscroll=false;
			scrollchanged=true;
			notifyparent();
		}
	}
	void VListLayout::scrollto(Window* w,float p){
		auto size=children.size();
		if(0.<=p&&p<=1.){
			if(size>displayitem){
				auto t=SDL_min((size_t)(p*size),size-displayitem);
				if(_scroll(w,t)){
					scrollrect.y=t*unit;
					redraw=true;
					notifyparent();
				}
			}
			return;
		}
		if(p<0.F){
			if(_scroll(w,0)){
				scrollrect.y=0.F;
				redraw=true;
				notifyparent();
				return;
			}
			return;
		}
		if(size>displayitem){
			auto t=size-displayitem;
			if(_scroll(w,t)){
				scrollrect.y=t*unit;
				redraw=true;
				notifyparent();
			}
		}
	}
	void VListLayout::scrollto(Window* w,size_t p){
		auto size=children.size();
		if(p<size){
			if(size>displayitem){
				auto t=SDL_min(p,size-displayitem);
				if(_scroll(w,t)){
					scrollrect.y=t*unit;
					redraw=true;
					notifyparent();
				}
			}
			return;
		}
		if(size>displayitem){
			auto t=size-displayitem;
			if(_scroll(w,t)){
				scrollrect.y=t*unit;
				redraw=true;
				notifyparent();
			}
		}
	}
	HListLayout::HListLayout(unsigned int ndisplay,SDL_Color back,
	std::initializer_list<Node*> children,float scrollpercent):
	ListLayout(ndisplay,back,children,scrollpercent){}
	void HListLayout::draw(Renderer* r,bool updatecache){
		auto w=rect.w,h=rect.h,x=rect.x;
		size_t i=begin;
		auto& arr=children;
		auto size=arr.size();
		auto sh=h*scrollpercent;
		if(redraw|updatecache){
			CacheNode::_draw(r,updatecache);
			redraw=false;
			notified=false;
			size_t t=SDL_min(displayitem,size),n=0;
			if(displayitem)unit=(float)w/displayitem;else unit=0.F;
			itemrect.x=x,itemrect.y=rect.y;
			itemrect.h=h-sh,itemrect.w=w/displayitem;
			r->copyF(cache,nullptr,&rect);
			r->setdrawcolor(background);
			r->fillrectF(&rect);
			if(size){
				while(n++<t){
					if(i==size)break;
					auto cur=arr[i++];
					(SDL_FRect&)cur->getrect()=itemrect;
					cur->forceredraw();
					cur->draw(r,true);
					itemrect.x+=itemrect.w;
				}
			}
			goto _hlistlayout_scrollchanged;
		}
		if(scrollchanged){
			_hlistlayout_scrollchanged:
			total=w*((float)begin/size);
			SDL_Rect _src{0,(int)(h-sh),(int)w,(int)sh};
			if(displayitem<size)scrollrect={x+total,rect.y+(h-sh),((float)displayitem/size)*w,round(sh)};
			else scrollrect={x+total,rect.y+(h-sh),w,round(sh)};
			r->setdrawcolor({0,0,0,255});
			SDL_FRect _{x,scrollrect.y,w,scrollrect.h};
			r->fillrectF(&_);
			r->copyF(cache,&_src,&_);
			auto back=background.a;
			r->setdrawcolor(background);
			r->fillrectF(&_);
			if(dragged)back>>=2;
			else if(inscroll)back>>=1;
			r->setdrawcolor({(Uint8)~background.r,(Uint8)~background.g,(Uint8)~background.b,back});
			r->fillrectF(&scrollrect);
			scrollchanged=false;
		}
		if(notified){
			notified=false;
			if(size){
				size_t t=displayitem,n=0;
				i=begin;
				while(n++<t){
					if(i==size){
						makecircle(r,rect);
						return;
					}
					auto cur=arr[i++];
					if(cur->confirmdraw())cur->draw(r,updatecache);
				}
			}
		}
		makecircle(r,rect);
	}
	void HListLayout::scroll(Window* w,int x,int y){
		if(x>0&&x>begin)return scrollto(w,(size_t)0);
		scrollto(w,begin-x);
	}
	void HListLayout::pressed(Window* w,SDL_FPoint& p){
		auto& r=rect;
		auto x=p.x,y=p.y,rx=r.x,rw=r.w;
		if(scrollrect.y<=y&&y<=r.y+r.h&&rx<=x&&x<=rx+rw){
			dragged=true;
			inscroll=true;
			scrollchanged=true;
			notifyparent();
			mousemove(w,p);
		}
	}
	void HListLayout::released(Window* w,SDL_FPoint& p){
		if(dragged){
			dragged=false;
			auto& r=rect;
			auto x=p.x,y=p.y,rx=r.x,rw=r.w;
			if(scrollrect.y>y||y>r.y+r.h||rx>x||x>rx+rw)inscroll=false;
			scrollchanged=true;
			notifyparent();
		}
	}
	void HListLayout::mousemove(Window* w,SDL_FPoint p){
		if(dragged){
			scrollto(w,(p.x-rect.x)/rect.w);
			return;
		}
		auto& r=rect;
		auto x=p.x,y=p.y,rx=r.x,rw=r.w;
		if(scrollrect.y<y&&y<=r.y+r.h&&rx<=x&&x<=rx+rw){
			scrollchanged=true;
			inscroll=true;
			notifyparent();
			return;
		}
		if(inscroll){
			inscroll=false;
			scrollchanged=true;
			notifyparent();
		}
	}
	void HListLayout::scrollto(Window* w,float p){
		auto size=children.size();
		if(0.<=p&&p<=1.){
			if(size>displayitem){
				auto t=SDL_min((size_t)(p*size),size-displayitem);
				if(_scroll(w,t)){
					scrollrect.x=t*unit;
					redraw=true;
					notifyparent();
				}
			}
			return;
		}
		if(p<0.F){
			if(_scroll(w,0)){
				scrollrect.x=0.F;
				redraw=true;
				notifyparent();
				return;
			}
			return;
		}
		if(size>displayitem){
			auto t=size-displayitem;
			if(_scroll(w,t)){
				scrollrect.x=t*unit;
				redraw=true;
				notifyparent();
			}
		}
	}
	void HListLayout::scrollto(Window* w,size_t p){
		auto size=children.size();
		if(p<size){
			if(size>displayitem){
				auto t=SDL_min(p,size-displayitem);
				if(_scroll(w,t)){
					scrollrect.x=t*unit;
					redraw=true;
					notifyparent();
				}
			}
			return;
		}
		if(size>displayitem){
			auto t=size-displayitem;
			if(_scroll(w,t)){
				scrollrect.x=t*unit;
				redraw=true;
				notifyparent();
			}
		}
	}
}